/*
EP3D is a real-time 3D planet engine , which in addition to providing 
substandard scene rendering and scene management, of course, it also 
provides some basic class libraries to build the entire virtual 
planet, or even the entire universe.

Copyright (C) 2010  Hongjiang Zhang	(zhjwyat@gmail.com)

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef EP3D_BOUNDINGBOX_H
#define EP3D_BOUNDINGBOX_H
#include "EP3DVector3.h"
#include "EP3DMatrix4.h"
#include "EP3DRay3.h"
#include "EP3DPair.h"
#include "EP3DLogger.h"

namespace EP3D
{
	template<class T>
	class BoundingBox
	{
	public:
		BoundingBox()
			:m_kMin(-0.5, -0.5, -0.5)
			,m_kMax(0.5, 0.5, 0.5)
		{
		}
		
		BoundingBox(const Vector3<T>& kMin, const Vector3<T>& kMax)
		{
			m_kMin = kMin;
			m_kMax = kMax;
		}

		BoundingBox(T minx, T miny, T minz, T maxx, T maxy, T maxz)
			:m_kMin(minx, miny, minz)
			,m_kMax(maxx, maxy, maxz)
		{
		}

		void Set(const Vector3<T>& kMin, const Vector3<T>& kMax)
		{
			m_kMin = kMin;
			m_kMax = kMax;
		}

		Vector3<T> GetCenter() const
		{
			return (m_kMin + m_kMax) * 0.5;
		}

		Vector3<T> GetExtents() const
		{
			return (m_kMax - m_kMin) * 0.5;
		}

		Vector3<T> GetSize() const
		{
			return m_kMax - m_kMin;
		}

		const Vector3<T>& GetMin() const
		{
			return m_kMin;
		}

		const Vector3<T>& GetMax() const
		{
			return m_kMax;
		}

		void SetExtents(const Vector3<T>& kCenter, const Vector3<T>& kExtents)
		{
			m_kMax = kCenter + kExtents;
			m_kMin = kCenter - kExtents;
		}

		bool Intersects(const BoundingBox<T>& b) const
		{
			if (m_kMax.x < b.m_kMin.x)
				return false;
			if (m_kMax.y < b.m_kMin.y)
				return false;
			if (m_kMax.z < b.m_kMin.z)
				return false;

			if (m_kMin.x > b.m_kMax.x)
				return false;
			if (m_kMin.y > b.m_kMax.y)
				return false;
			if (m_kMin.z > b.m_kMax.z)
				return false;

			return true;
		}

		Pair<bool, T> Intersects(const Ray3<T>& ray) const
		{
			T lowt = (T)0;
			T t;
			bool hit = false;

			Vector3<T>& hitpoint;
			const Vector3<T>& min = m_kMin;
			const Vector3<T>& max = m_kMax;
			const Vector3<T>& rayorig = ray.m_rkOrigin;
			const Vector3<T>& raydir = ray.m_rkDirection;

			if ( rayorig > min && rayorig < max )
			{
				return Pair<true, (T)0>;
			}

			// Check each face in turn, only check closest 3
			// Min x
			if (rayorig.x < min.x && raydir.x > 0)
			{
				t = (min.x - rayorig.x) / raydir.x;
				if (t > 0)
				{
					// Substitute t back into ray and check bounds and dist
					hitpoint = rayorig + raydir * t;
					if (hitpoint.y >= min.y && hitpoint.y <= max.y &&
						hitpoint.z >= min.z && hitpoint.z <= max.z &&
						(!hit || t < lowt))
					{
						hit = true;
						lowt = t;
					}
				}
			}
			// Max x
			if (rayorig.x > max.x && raydir.x < 0)
			{
				t = (max.x - rayorig.x) / raydir.x;
				if (t > 0)
				{
					// Substitute t back into ray and check bounds and dist
					hitpoint = rayorig + raydir * t;
					if (hitpoint.y >= min.y && hitpoint.y <= max.y &&
						hitpoint.z >= min.z && hitpoint.z <= max.z &&
						(!hit || t < lowt))
					{
						hit = true;
						lowt = t;
					}
				}
			}
			// Min y
			if (rayorig.y < min.y && raydir.y > 0)
			{
				t = (min.y - rayorig.y) / raydir.y;
				if (t > 0)
				{
					// Substitute t back into ray and check bounds and dist
					hitpoint = rayorig + raydir * t;
					if (hitpoint.x >= min.x && hitpoint.x <= max.x &&
						hitpoint.z >= min.z && hitpoint.z <= max.z &&
						(!hit || t < lowt))
					{
						hit = true;
						lowt = t;
					}
				}
			}
			// Max y
			if (rayorig.y > max.y && raydir.y < 0)
			{
				t = (max.y - rayorig.y) / raydir.y;
				if (t > 0)
				{
					// Substitute t back into ray and check bounds and dist
					hitpoint = rayorig + raydir * t;
					if (hitpoint.x >= min.x && hitpoint.x <= max.x &&
						hitpoint.z >= min.z && hitpoint.z <= max.z &&
						(!hit || t < lowt))
					{
						hit = true;
						lowt = t;
					}
				}
			}
			// Min z
			if (rayorig.z < min.z && raydir.z > 0)
			{
				t = (min.z - rayorig.z) / raydir.z;
				if (t > 0)
				{
					// Substitute t back into ray and check bounds and dist
					hitpoint = rayorig + raydir * t;
					if (hitpoint.x >= min.x && hitpoint.x <= max.x &&
						hitpoint.y >= min.y && hitpoint.y <= max.y &&
						(!hit || t < lowt))
					{
						hit = true;
						lowt = t;
					}
				}
			}
			// Max z
			if (rayorig.z > max.z && raydir.z < 0)
			{
				t = (max.z - rayorig.z) / raydir.z;
				if (t > 0)
				{
					// Substitute t back into ray and check bounds and dist
					hitpoint = rayorig + raydir * t;
					if (hitpoint.x >= min.x && hitpoint.x <= max.x &&
						hitpoint.y >= min.y && hitpoint.y <= max.y &&
						(!hit || t < lowt))
					{
						hit = true;
						lowt = t;
					}
				}
			}

			return Pair<bool, T>(hit, lowt);
		}

		bool Contains(const BoundingBox<T>& b) const
		{
			if (m_kMax.x < b.m_kMax.x)
				return false;
			if (m_kMax.y < b.m_kMax.y)
				return false;
			if (m_kMax.z < b.m_kMax.z)
				return false;

			if (m_kMin.x > b.m_kMin.x)
				return false;
			if (m_kMin.y > b.m_kMin.y)
				return false;
			if (m_kMin.z > b.m_kMin.z)
				return false;

			return true;
		}

		bool Contains(const Vector3<T>& p) const
		{
			return (
				p.x >= m_kMin.x &&
				p.x <= m_kMax.x &&
				p.y >= m_kMin.y &&
				p.y <= m_kMax.y &&
				p.z >= m_kMin.z &&
				p.z <= m_kMax.z);
		}

		void Merge(const BoundingBox<T>& b)
		{
			Merge(b.m_kMax);
			Merge(b.m_kMin);
		}

		void Merge(const Vector3<T>& v)
		{
			if (v.x > m_kMax.x) m_kMax.x = v.x;
			if (v.y > m_kMax.y) m_kMax.y = v.y;
			if (v.z > m_kMax.z) m_kMax.z = v.z;

			if (v.x < m_kMin.x) m_kMin.x = v.x;
			if (v.y < m_kMin.y) m_kMin.y = v.y;
			if (v.z < m_kMin.z) m_kMin.z = v.z;
		}

		void Scale(const Vector3<T>& s)
		{
			m_kMax *= s;
			m_kMin *= s;
		}

		void Transform(const Matrix4<T>& mat)
		{
			Vector3<T> vTemp, vMax, vMin;
			for (uint ui = 0; ui < 8; ++ui) {
				vTemp = GetCorner(ui) * mat;
				if (vTemp.x > vMax.x)	vMax.x = vTemp.x;
				if (vTemp.y > vMax.y)	vMax.y = vTemp.y;
				if (vTemp.z > vMax.z)	vMax.z = vTemp.z;
				if (vTemp.x < vMin.x)	vMin.x = vTemp.x;
				if (vTemp.y < vMin.y)	vMin.y = vTemp.y;
				if (vTemp.z < vMin.z)	vMin.z = vTemp.z;
			}

			Set(vMin, vMax);
		}

		uint GetVolume() const
		{
			Vector3<T>& diff = m_kMax - m_kMin;
			return diff.x * diff.y * diff.z;
		}

		/************************************************************************/
		/*
				   6-----7
				  /|    /|
				 / |   / |
				5-----4  |
				|  0--|--3
				| /   | /
			z   |/    |/
			| 	1-----2
			|
			o-----x
		   /
		 y/
		*/   
		/************************************************************************/

		Vector3<T> GetCorner(uint uiIndex) const
		{
			LogAssert(uiIndex < 8);
			switch (uiIndex) 
			{
			case 0:		return m_kMin;
			case 1:		return Vector3<T>(m_kMin.x, m_kMax.y, m_kMin.z);
			case 2:		return Vector3<T>(m_kMax.x, m_kMax.y, m_kMin.z);
			case 3:		return Vector3<T>(m_kMax.x, m_kMin.y, m_kMin.z);
			case 4:		return m_kMax;
			case 5:		return Vector3<T>(m_kMin.x, m_kMax.y, m_kMax.z);
			case 6:		return Vector3<T>(m_kMin.x, m_kMin.y, m_kMax.z);
			default:	return Vector3<T>(m_kMax.x, m_kMin.y, m_kMax.z);
			}
		}

	public:
		Vector3<T> m_kMin;
		Vector3<T> m_kMax;
	};

	typedef BoundingBox<f32> BBox3f;
	typedef BoundingBox<f64> BBox3d;
}
#endif